# Copyright 2010 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Author: tu@tulrich.com (Thatcher Ulrich)

"""Code for analyzing uploaded data.
"""

import os
from django.utils import simplejson
from google.appengine.api import users
from google.appengine.ext import db
from google.appengine.ext.webapp import template
import distro
import processor
import re
import upload


def make_tag(label):
  return label.lower().replace(" ", "_")


def append_browser_tags(tags, r):
  if not hasattr(r, "reporter_userAgent"):
    tags.append("browser_other")
    return

  ua = getattr(r, "reporter_userAgent")
  browser_signatures = [
    "Chrome",
    "Firefox",
    "Safari",
    "Opera",
    "MSIE",
    "Camino",
    "Iceweasel",
    "SketchUp",
    "iWeb",
    "Konqueror",
    "BlackBerry",
    "NetFront",
    "Epiphany"
    ]
  for b in browser_signatures:
    if ua.find(b) != -1:
      tags.append(make_tag("browser_" + b))
      return

  tags.append("browser_other")


def append_os_tags(tags, r):
  if not hasattr(r, "reporter_userAgent"):
    tags.append("os_other")
    return

  ua = getattr(r, "reporter_userAgent")
  os_signatures = [
    "Windows",
    "Android",  # Check this before "Linux"!
    "Linux",
    "Macintosh",
    "PLAYSTATION",
    "BlackBerry",
    "Nokia",
    "NOKIA",
    "iPhone",
    "iPod",
    "iPad",
    "J2ME",
    "Wii",
    "SonyEricsson",
    "SAMSUNG",
    "FreeBSD",
    "SymbianOS",
    "SunOS"
    ]
  match = None
  for os in os_signatures:
    if ua.find(os) != -1:
      match = make_tag("os_" + os)
      break
  if not match:
    tags.append("os_other")
    return
  tags.append(match)

  if match == "os_iphone" or match == "os_ipod" or match == "os_ipad":
    tags.append("os_ios")
    tags.append("os_mobile")

  if match == "os_android":
    tags.append("os_mobile")


def append_gpu_tags(tags, r):
  if not hasattr(r, "reporter_GL_RENDERER"):
    tags.append("gpu_other")
    return
  renderer = getattr(r, "reporter_GL_RENDERER") or ""

  gpu_signatures = [
    "NVIDIA",
    "Quadro",
    "GeForce",
    "ANGLE",
    "Intel",
    "ATI"
    ]
  match = None
  for gpu in gpu_signatures:
    if renderer.find(gpu) != -1:
      match = make_tag("gpu_" + gpu)
      break

  if not match:
    tags.append("gpu_other")
    return
  tags.append(match)

  # Combine some subcategories.
  if match == "gpu_quadro" or match == "gpu_geforce":
    tags.append("gpu_nvidia")


def get_tags(r):
  """Return the tags associated with this report.
  """
  tags = ["all"]
  append_browser_tags(tags, r)
  append_os_tags(tags, r)
  append_gpu_tags(tags, r)
  return tags


def format_number(x):
  try:
    return str(int(x))
  except ValueError:
    return str(float(x))


def update_tagsets_from_report(tagsets, r):
  """Update the tagsets with the data from this report.
  """
  tags = get_tags(r)
  for t in tags:
    if not tagsets.has_key(t):
      tagsets[t] = {}

    tagdata = tagsets[t]
    for k in r.dynamic_properties():
      if k.startswith("metric_"):
        property = k[len("metric_"):]
        value = float(getattr(r, k))  # TODO protect this against NaN's
        if not tagdata.has_key(property):
          tagdata[property] = distro.Distro()
        tagdata[property].push_data(value)


def format_distro(metric, d, userstat=None):
  """Generate a visualization of the given distribution.

  If userstat is not None, it should be a user statistic we will plot
  in green.

  Currently using inline SVG to make a box plot, seems good.
  """
  d.update_estimates()

  # TODO: enable this check once we have a reasonable number of data points.
  #min_data_points = 100
  #if d.get_data_count() < min_data_points:
  #  # Not enough data for anonymity.
  #  return "(insufficient data, fewer than %s measurements)" % min_data_points

  # TODO: move this plot over to the client (and probably switch to
  # <canvas>) so it can update the green user-markers without having
  # to upload.
  template = """
    <svg xmlns="http://www.w3.org/2000/svg" version="1.1"
        viewBox="0 0 500 28"
        style="width:500px; height:28px; padding: 0px; margin: 0px;">
      <!-- <circle cx="50" cy="50" r="30" style="fill:url(#gradient)" /> -->
      <g stroke="blue" stroke-width="1">
        <line x1="%(x0)s" y1="3" x2="%(x0)s" y2="18"/>
        <line x1="%(x0)s" y1="10.5" x2="%(x1)s" y2="10.5"/>
        <line x1="%(x2)s" y1="2" x2="%(x2)s" y2="19"/>
        <line x1="%(x3)s" y1="10.5" x2="%(x4)s" y2="10.5"/>
        <line x1="%(x4)s" y1="3" x2="%(x4)s" y2="18"/>
        <rect x="%(x1)s" y="3" width="%(x3_minus_x1)s" height="15" fill="none"/>
      </g>
      <circle cx="%(xmin)s" cy="10.5" r="3" fill="red" stroke="none"/>
      <circle cx="%(xmax)s" cy="10.5" r="3" fill="red" stroke="none"/>
      <circle cx="%(userx)s" cy="10.5" r="5" fill="none" stroke="green" stroke-width="3"/>
      <!-- outline <rect x="0" y="0" width="500" height="25" stroke="black" fill="none"/> -->
      <text x="%(x2)s" y="27" text-anchor="middle" font-family="monospace" font-size="10">%(x2val)s</text>
      <text x="490" y="14.5" text-anchor="end" font-family="monospace" font-size="10">n=%(numdata)s</text>
    </svg>
"""

  pad = 20
  scale = 1
  if d.get_quantile(2) > 0:
    scale = ((500.0 / 2) - pad) / d.get_quantile(2)
  offset = pad
  def trans(x):
    return x * scale + offset

  userx = -100
  if userstat != None:
    userx = trans(userstat)

  return template % { "xmin": trans(d.get_min()),
                      "x0": trans(d.get_quantile(0)),
                      "x1": trans(d.get_quantile(1)),
                      "x2": trans(d.get_quantile(2)),
                      "x3": trans(d.get_quantile(3)),
                      "x3_minus_x1": trans(d.get_quantile(3)) - trans(d.get_quantile(1)),
                      "x4": trans(d.get_quantile(4)),
                      "xmax": trans(d.get_max()),
                      "x2val": format_number(d.get_quantile(2)),
                      "numdata": str(int(d.get_data_count())),
                      "userx": userx,
                        }

  
def update_tagsets(tagsets, later_than):
  """Updates the tagged datasets, using reports timestamped > later_than.
  
  Goes in order.  Returns the timestamp of the latest processed report.
  """
  q = db.GqlQuery("SELECT * FROM Report WHERE timestamp > :1 "
                  "ORDER BY timestamp ASC LIMIT 1000",
                  later_than) #.isoformat(' '))
  reports = q.fetch(1000)
  for r in reports:
    update_tagsets_from_report(tagsets, r)
  if len(reports) == 0:
    return later_than
  else:
    return reports[-1].timestamp


def handler(app):
  dump = dump_interesting_tagsets()

  template_values = { "dump": dump,
                      "datasets": json_interesting_tagsets()
                      }
  path = os.path.join(os.path.dirname(__file__), 'analysis.html')
  app.response.out.write(template.render(path, template_values))


INTERESTING_TAGS = ["all",
                    "browser_firefox",
                    "browser_chrome",
                    "browser_safari",
                    "gpu_nvidia",
                    "gpu_ati",
                    "gpu_intel",
                    "gpu_angle",
                    "gpu_other",
                    "os_windows",
                    "os_macintosh",
                    "os_linux",
                    "os_ios",
                    "os_android"
                    ]


def get_data_handler(app):
  """Return JSON of requested tags.
  """
  tag_arg = app.request.get("tags", "")
  if not tag_arg:
    tags = INTERESTING_TAGS;
  else:
    tags = tag_arg.split(",")
  
  app.response.out.write(json_tagsets_for_tags(tags))


def dump_interesting_tagsets(userstats=None):
  return dump_tagsets_for_tags(INTERESTING_TAGS)


def json_interesting_tagsets(userstats=None):
  return json_tagsets_for_tags(["all",
                                "browser_firefox",
                                "browser_chrome",
                                "browser_safari",
                                "gpu_nvidia",
                                "gpu_ati",
                                "gpu_intel",
                                "gpu_angle",
                                "gpu_other",
                                "os_windows",
                                "os_macintosh",
                                "os_linux",
                                "os_ios",
                                "os_android"
                                ])


def dump_tagsets(tagsets, userstats=None):
  dump = ""
  for t in tagsets:
    dump += "<hr/>tag = <b>%s</b><br/>\n" % t
    dump += '<table rules="rows" frame="border">\n'
    for metric in sorted(tagsets[t].keys()):
      userstat = None
      if userstats and userstats["metric"].has_key(metric):
        userstat = userstats["metric"][metric]
      dump += (('<tr><td style="font-family: monospace; font-size: 10pt;">%s</td>' % metric) +
               '<td style="padding: 0px">' + format_distro(metric, tagsets[t][metric], userstat) + "</td></tr>\n")
    dump += "</table>\n"
  return dump


def json_tagsets(tags, tagsets, userstats=None):
  """Preserve the order from tags[].
  """
  val = []
  for t in tags:
    if not tagsets.has_key(t): continue
    dataset = tagsets[t]
    subval = { "tag": t, "metric": {} }
    for metric in dataset:
      distro = dataset[metric]
      subval["metric"][metric] = json_distro(distro)
    val.append(subval)
  return simplejson.dumps(val)


def json_distro(distro):
  distro.update_estimates()
  val = { "type": "box",
          "min": distro.get_min(),
          "q0": distro.get_quantile(0),
          "q1": distro.get_quantile(1),
          "q2": distro.get_quantile(2),
          "q3": distro.get_quantile(3),
          "q4": distro.get_quantile(4),
          "max": distro.get_max(),
          "n": distro.get_data_count()
          }
  return val

def dump_tagsets_for_tags(tags, userstats=None):
  """
  """
  q = db.GqlQuery("SELECT * FROM Processor WHERE tag in :1 LIMIT 1000", tags)
  processors = q.fetch(1000)

  tagsets = {}

  for p in processors:
    tagdata = processor.to_tagdata(p)
    tagsets[p.tag] = tagdata

  # dump tagsets
  return dump_tagsets(tagsets, userstats)
  

def json_tagsets_for_tags(tags, userstats=None):
  """
  """
  q = db.GqlQuery("SELECT * FROM Processor WHERE tag in :1 LIMIT 1000", tags)
  processors = q.fetch(1000)

  tagsets = {}

  for p in processors:
    tagdata = processor.to_tagdata(p)
    tagsets[p.tag] = tagdata

  # dump tagsets
  return json_tagsets(tags, tagsets, userstats)
  
